package com.bzframework.pro.io.manager;

import cn.hutool.core.io.FileUtil;
import com.bzframework.pro.io.configuration.LocalFileProperties;
import com.bzframework.pro.io.entity.FileEntity;
import com.bzframework.pro.io.entity.FileInfo;
import com.bzframework.pro.io.service.FileEntityService;
import io.netty.buffer.ByteBufUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.id.IDGenerator;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.ContentDisposition;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Map;

import static java.nio.file.StandardOpenOption.CREATE_NEW;


/**
 * @Author bingzhong
 * @Date 2024/12/6 15:51
 * @Description 上传到本机
 */
@RequiredArgsConstructor
public class DefaultFileManager implements FileManager {

    private final LocalFileProperties localFileProperties;

    private final FileEntityService fileEntityService;

    @Override
    public Mono<FileInfo> saveFile(FilePart filePart) {

        String extension = FileUtil.extName(filePart.filename());

        boolean idDeny = localFileProperties.getDenyFiles()
                                            .stream()
                                            .anyMatch(file -> file.equals(extension));
        if (idDeny) {
            return Mono.error(new BusinessException("error file extension:" + extension + " not support"));
        }

        String storagePath = localFileProperties.getBaseStoragePath();
        String fileId = IDGenerator.SNOW_FLAKE_STRING.generate();
        LocalDate now = LocalDate.now();
        String fileStoragePath = now.format(DateTimeFormatter.BASIC_ISO_DATE)
                + "/" + fileId + "." + extension;

        FileEntity fileEntity = new FileEntity();
        fileEntity.setId(fileId);
        fileEntity.setName(filePart.filename());
        fileEntity.setExtension(extension);
        fileEntity.setStoragePath(fileStoragePath);
        fileEntity.setCreateTime(System.currentTimeMillis());
        MessageDigest md5Digest = DigestUtils.getMd5Digest();
        MessageDigest sha256Digest = DigestUtils.getSha256Digest();

        String accessKey = IDGenerator.RANDOM.generate();
        Map<String, Object> others = Collections.singletonMap("accessKey", accessKey);
        fileEntity.setOthers(others);

        Path path = Paths.get(storagePath, fileStoragePath);
        FileUtil.mkParentDirs(path);
        return filePart.content()
                       .map(dataBuffer -> {
                           md5Digest.update(dataBuffer.asByteBuffer());
                           sha256Digest.update(dataBuffer.asByteBuffer());
                           return dataBuffer;
                       })
                       .as(dataBufferStream -> DataBufferUtils.write(
                               filePart.content(),
                               path,
                               StandardOpenOption.WRITE,
                               CREATE_NEW,
                               StandardOpenOption.TRUNCATE_EXISTING))
                       .then(Mono.defer(() -> {
                           File savedFile = path.toFile();
                           if (!savedFile.exists()) {
                               return Mono.error(new BusinessException("file saved failed"));
                           }
                           fileEntity.setLength(savedFile.length());
                           fileEntity.setMd5(ByteBufUtil.hexDump(md5Digest.digest()));
                           fileEntity.setSha256(ByteBufUtil.hexDump(sha256Digest.digest()));
                           FileInfo fileInfo = FastBeanCopier.copy(fileEntity, FileInfo.class);
                           return fileEntityService.save(fileEntity)
                                                   .thenReturn(fileInfo.withBasePath(localFileProperties.getBaseUrl()));
                       }));
    }

    @Override
    @SneakyThrows
    public Mono<Void> readFile(String fileId, ServerWebExchange webExchange) {

        if (fileId.contains(".")) {
            fileId = fileId.substring(0, fileId.indexOf("."));
        }

        webExchange.getResponse()
                   .getHeaders()
                   .setContentType(MediaType.APPLICATION_OCTET_STREAM);

        return fileEntityService.findById(fileId)
                                .switchIfEmpty(Mono.error(new BusinessException("file not found")))
                                .flatMap(fileEntity -> {

                                    // 验证文件
                                    String accessKey = webExchange.getRequest()
                                                                  .getQueryParams()
                                                                  .getFirst("accessKey");
                                    String storageAccessKey = String.valueOf(fileEntity.getOthers().getOrDefault("accessKey", ""));

                                    if (StringUtils.isAnyEmpty(accessKey, storageAccessKey) || !StringUtils.equals(accessKey, storageAccessKey)) {
                                        return Mono.error(new BusinessException("accessKey not correct"));
                                    }

                                    //文件名
                                    webExchange.getResponse().getHeaders().setContentDisposition(
                                            ContentDisposition
                                                    .attachment()
                                                    .filename(fileEntity.getName(), StandardCharsets.UTF_8)
                                                    .build()
                                    );
                                    String storagePath = fileEntity.getStoragePath();
                                    String filePath = localFileProperties.getBaseStoragePath() + "/" + storagePath;
                                    return webExchange.getResponse()
                                                      .writeWith(DataBufferUtils.read(new FileSystemResource(Paths.get(filePath)),
                                                                                      new DefaultDataBufferFactory(), 4096));
                                })
                                .subscribeOn(Schedulers.boundedElastic());
    }
}
